package nachos.threads;

import nachos.machine.*;

/**
 * An implementation of condition variables that disables interrupt()s for
 * synchronization.
 *
 * <p>
 * You must implement this.
 *
 * @see	nachos.threads.Condition
 */
public class Condition2 {
    /**
     * Allocate a new condition variable.
     *
     * @param	conditionLock	the lock associated with this condition
     *				variable. The current thread must hold this
     *				lock whenever it uses <tt>sleep()</tt>,
     *				<tt>wake()</tt>, or <tt>wakeAll()</tt>.
     */
    public Condition2(Lock conditionLock) {
	this.conditionLock = conditionLock;
    }

    /**
     * Atomically release the associated lock and go to sleep on this condition
     * variable until another thread wakes it using <tt>wake()</tt>. The
     * current thread must hold the associated lock. The thread will
     * automatically reacquire the lock before <tt>sleep()</tt> returns.
     */
    public void sleep() {
	Lib.assertTrue(conditionLock.isHeldByCurrentThread());

	conditionLock.release();

	boolean intStatus = Machine.interrupt().disable();
   waitQueue.waitForAccess(KThread.currentThread());
   KThread.sleep();
	Machine.interrupt().restore(intStatus);

	conditionLock.acquire();
    }

    /**
     * Wake up at most one thread sleeping on this condition variable. The
     * current thread must hold the associated lock.
     */
    public void wake() {
	Lib.assertTrue(conditionLock.isHeldByCurrentThread());
	boolean intStatus = Machine.interrupt().disable();

	KThread thread = waitQueue.nextThread();
	if (thread != null) {
	    thread.ready();
	}
	Machine.interrupt().restore(intStatus);
    }

    /**
     * Wake up all threads sleeping on this condition variable. The current
     * thread must hold the associated lock.
     */
    public void wakeAll() {
	Lib.assertTrue(conditionLock.isHeldByCurrentThread());
   while( !waitQueue.isEmpty() ) {
	    wake();
   }
    }

    private Lock conditionLock;
    private ThreadQueue waitQueue =
	ThreadedKernel.scheduler.newThreadQueue(false);

    public static void selfTest() {
   /*
    * Condition Variable Test
    * This test shows that the implementation of Condition Variables is
    * working properly.  You should expect to see something like this:
    *
    * ================================================
    * Running CondTestWaitThread
    * condstate = false, sleeping...
    * Running CondTestWorkThread
    * In Critical Section 1
    * Yielding thread: Condition Variable Worker (#6)
    * [2] Working... 1
    * Yielding thread: worker2 thread (#3)
    * [3] Working... 1
    * Yielding thread: worker3 thread (#4)
    * In Critical Section 2
    * Yielding thread: Condition Variable Worker (#6)
    * ....
    * In Critical Section 6
    * CondTestWorkThread Done
    * [2] Working... 7
    * Yielding thread: worker2 thread (#3)
    * [3] Working... 7
    * Yielding thread: worker3 thread (#4)
    * CondTestWaitThread Done
    * ================================================
    *
    * It shows that the Condition Variable Worker is able to allow other
    * threads to execute (namely the ones from the previous self-test), but
    * it ensures that is maintains a critical section with the thread(s) who
    * share its lock.
    *
    */
   KThread condwaiter1 = new KThread( new CondTestWaitThread() );
   KThread condwaiter2 = new KThread( new CondTestWaitThread() );
   KThread condwaiter3 = new KThread( new CondTestWaitThread() );
   KThread condworker = new KThread( new CondTestWorkThread() );
   condwaiter1.setName("Condition Variable Waiter 1" );
   condwaiter2.setName("Condition Variable Waiter 2" );
   condwaiter3.setName("Condition Variable Waiter 3" );
   condworker.setName("Condition Variable Worker" );
   condwaiter1.fork();
   condwaiter2.fork();
   condwaiter3.fork();
   condworker.fork();

   condwaiter1.join();
   condwaiter2.join();
   condwaiter3.join();
   condworker.join();
    }

    private static Lock condlock = new Lock();
    private static boolean condstate = false;
    private static Condition2 cv = new Condition2( condlock );

    private static class CondTestWaitThread implements Runnable {
       public CondTestWaitThread() {
       }

       public void run() {
          System.out.println( "Running CondTestWaitThread" );
          condlock.acquire();
          while( condstate == false ) {
             System.out.println( "condstate = false, sleeping..." );
             cv.sleep();
          }
          cv.wakeAll();
          condlock.release();
          System.out.println( "CondTestWaitThread Done" );
       }
    }

    private static class CondTestWorkThread implements Runnable {
       public CondTestWorkThread() {
       }

       public void run() {
          System.out.println( "Running CondTestWorkThread" );
          condlock.acquire();
          //critical section
          System.out.println( "In Critical Section 1" );
          KThread.currentThread().yield();
          System.out.println( "In Critical Section 2" );
          KThread.currentThread().yield();
          System.out.println( "In Critical Section 3" );
          KThread.currentThread().yield();
          System.out.println( "In Critical Section 4" );
          KThread.currentThread().yield();
          System.out.println( "In Critical Section 5" );
          KThread.currentThread().yield();
          System.out.println( "In Critical Section 6" );
          KThread.currentThread().yield();
          condstate = true;
          cv.wakeAll();
          condlock.release();
          System.out.println( "CondTestWorkThread Done" );
       }
    }


}
